﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Linq.Expressions;
using System.Xml.Linq;
using CoolCode.Reflection;

namespace CoolCode.Data.Xml
{
    public class ExpressionBuilder
    {
        public Expression<Func<XElement, T, bool>> PrimaryKey<T>()
        {
            Type type = typeof(T);
            string[] primaryKeys = GetPrimaryKeys<T>();
            if (primaryKeys.Length == 0) return Where<T>();
            return Where<T>(primaryKeys);
        }

        private string[] GetPrimaryKeys<T>()
        {
            Type type = typeof(T);
            //1.查找linq to sql 实体的主键
            string[] primaryKeys = BLReflection.GetPropFrom<System.Data.Linq.Mapping.ColumnAttribute, T>(c => c.IsPrimaryKey);
            //2.查找框架定义的主键
            if (primaryKeys.Length == 0)
            {
                PrimaryKeyAttribute[] pkAttributes = (PrimaryKeyAttribute[])type.GetCustomAttributes(typeof(PrimaryKeyAttribute), false);
                if (pkAttributes.Length > 0)
                {
                    primaryKeys = pkAttributes[0].Key.Split(',');
                }
            }

            return primaryKeys;
        }

        public Expression<Func<XElement, T, bool>> Where<T>()
        {
            string[] props = getPropertyNames(typeof(T));
            return Where<T>(props);
        }

        public Expression<Func<XElement, T, bool>> Where<T>(string[] props)
        {
            Type type = typeof(T);
            // parameters 
            ParameterExpression xElementExpr = Expression.Parameter(typeof(XElement), "s");
            ParameterExpression entityExpr = Expression.Parameter(type, "e");
            //convertor
            var convert = typeof(XmlConvert).GetMethod("SerializeObject", new Type[] { typeof(object) });
            // method                           
            var m = typeof(XElement).GetMethod("Element", new Type[] { typeof(XName) });
            Expression cond = Expression.Constant(true);
            if (props.Length > 0)
            {
                for (int i = 0; i < props.Length; i++)
                {
                    var mcall = Expression.Call(xElementExpr, m,
                            new Expression[] { Expression.Constant((XName)props[i], typeof(XName)) }
                           );

                    var prop = Expression.Property(mcall, "Value");

                    var value = Expression.Call(null, convert, new Expression[]{
                           Expression.Convert(Expression.Property(entityExpr, props[i]), typeof(object))
                        });

                    cond = BinaryExpression.And(cond, BinaryExpression.Equal(prop, value));
                }
            }
            else
            {
                cond = Expression.Constant(false);
            }
            // lambda
            var lambda = Expression.Lambda<Func<XElement, T, bool>>(cond, new ParameterExpression[] { xElementExpr, entityExpr });
            return lambda;
        }

        public Expression<Func<T, XElement>> ToXElementExpression<T>()
        {
            Type type = typeof(T);
            string[] props = getPropertyNames(type);
            // parameters  
            ParameterExpression entityExpr = Expression.Parameter(type, "e");
            // constructor   
            var constructor = typeof(XElement).GetConstructor(new Type[] { typeof(XName),  typeof(  object[]) });
            var constructor2 = typeof(XElement).GetConstructor(new Type[] { typeof(XName), typeof(object) });
            var addMethod = typeof(XElement).GetMethod("Add", new Type[] { typeof(object) });
            //convertor
            var convert = typeof(XmlConvert).GetMethod("SerializeObject", new Type[] { typeof(object) });
            // constructor expression
            NewExpression constructorExpr;

            if (props.Length > 0)
            {
                // parameters for constructor  
                List<Expression> paramExpr = new List<Expression>(props.Length + 1);
                //paramExpr.Add(Expression.Constant((XName)type.Name));

                for (int i = 0; i < props.Length; i++)
                {
                    var propElement = Expression.New(constructor2,
                        Expression.Constant((XName)props[i]),
                        Expression.Call(null, convert, new Expression[]{
                            Expression.Convert(Expression.Property(entityExpr, props[i]), typeof(object)) 
                        })
                    );
                    paramExpr.Add(Expression.Convert( propElement, typeof(object)));
                }
                var otherParamExpr= Expression.NewArrayInit(typeof(object), paramExpr);
                constructorExpr = Expression.New(constructor, 
                    Expression.Constant((XName)type.Name),
                    otherParamExpr);
            }
            else
            {  
                constructorExpr = Expression.New(constructor,
                    Expression.Constant((XName)type.Name),
                    Expression.Constant((object[])null)
                );
            }
            // lambda
            var lambda = Expression.Lambda<Func<T, XElement>>(constructorExpr, new ParameterExpression[] { entityExpr });
            return lambda;
        }

        private string[] getPropertyNames(Type type)
        {
            return type.GetProperties()
                .Where(c => c.CanRead && c.CanWrite)
                .Select(c => c.Name)
                .ToArray();
        }

        /*
        public Expression<Func<XElement, bool>> Where(string[] props, string[] args)
        {
            // parameters 
            ParameterExpression s = Expression.Parameter(typeof(XElement), "s");
            // method                           
            var m = typeof(XElement).GetMethod("Element", new Type[] { typeof(XName) });
            Expression cond = Expression.Constant(true);
            for (int i = 0; i < props.Length; i++)
            {
                var mcall = Expression.Call(s, m,
                        new Expression[] { Expression.Constant((XName)props[i], typeof(XName)) }
                       );
                var prop = Expression.Property(mcall, "Value");
                var value = Expression.Constant(args[i], typeof(string));
                cond = BinaryExpression.And(cond, BinaryExpression.Equal(prop, value));
            }
            // lambda
            var lambda = Expression.Lambda<Func<XElement, bool>>(cond, new ParameterExpression[] { s });
            return lambda;
        }
         */

    }
}
